#include "stdafx.h"
#include "ServerHandler.h"
#include "ServerSystem.h"

#include "Core\ClientConnection.h"
#include "Core\XMLConnection.h"
#include "Core\CoreXMLHandlerManager.h"
#include "Core\CoreXMLHandler.h"
#include "Core\Packages.h"
#include "Core\Logger.h"

#include "Poco\File.h"

namespace ExamSystem
{
namespace Server
{
    CServerHandler::CServerHandler(CServerSystem *serverSystem)
        : CSystemHandler(serverSystem, serverSystem->m_serverConnection->GetXMLConnection())
    {
        RegisterPackageTypes();
    }

    CServerHandler::~CServerHandler(void)
    {

    }

    void CServerHandler::RegisterPackageTypes(void)
    {
        m_packageHandlers[CResultPackage::packageType] = &CServerHandler::ResultPackageHandler;
        m_packageHandlers[CDatabasePackage::packageType] = &CServerHandler::DatabasePackageHandler;
        m_packageHandlers[COrderPackage::packageType] = &CServerHandler::OrderPackageHandler;
        m_packageHandlers[CMessagePackage::packageType] = &CServerHandler::MessagePackageHandler;
        m_packageHandlers[CInformationPackage::packageType] = &CServerHandler::InformationPackageHandler;
        m_packageHandlers[CRedirectPackage::packageType] = &CServerHandler::ResultPackageHandler;
        m_packageHandlers[CIDAllocPackage::packageType] = &CServerHandler::IDAllocPackageHandler;

        SharedPtr<CCoreXMLHandler> &manager = m_xmlConnection->GetXMLHandler();
        for (std::map<std::string, PackageHandler>::iterator it = m_packageHandlers.begin();
            it != m_packageHandlers.end(); it++)
            manager->RegisterStanzaHandler(it->first, this);
    }

    void CServerHandler::ProcessPackage(void)
    {
        CSystemHandler::ProcessPackage();
        std::map<std::string, PackageHandler>::iterator it = 
            m_packageHandlers.find(m_currentPackage->GetPackageType());

        if (it == m_packageHandlers.end())
        {
            // we don't handler other packages
            systemLogger.warning("Received wrong package: " + m_currentPackage->GetPackageType());
            return;
        }
        (this->*(it->second))();
    }

    void CServerHandler::StartHandler(void)
    {
        //// send server info
        //CPackagePtr info = packageManager.CreatePackage(CServerInfoPackage::packageType);
        //static_cast<CServerInfoPackage *>(info.get())->m_port = m_serverSystem->GetListeningPort();
        //info->WriteToStream(m_xmlConnection->GetSocketOutputStream());

        // send system version
        SharedPtr<CPackage> pkg = packageManager.CreatePackage(CVersionPackage::packageType);
        static_cast<CVersionPackage *>(pkg.get())->m_versionType = CVersionPackage::System;
        pkg->WriteToStream(m_xmlConnection->GetSocketOutputStream());

        m_presentState = stateCheckingSystemVersion;
    }

    void CServerHandler::ResultPackageHandler(void)
    {
        CResultPackage &result = StaticCast<CResultPackage>(m_currentPackage);
        switch (m_presentState)
        {
        case stateCheckingSystemVersion:
            if (result.m_result == CResultPackage::Failed)
            {
                // check failed the connection will be closed
                // inform the system that the version check is failed
                eventVersionCheckFailed(this, this);
            }
            else
            {               
                // send database version
                SharedPtr<CPackage> pkg = packageManager.CreatePackage(CVersionPackage::packageType);
                static_cast<CVersionPackage *>(pkg.get())->m_versionType = CVersionPackage::Database;
                static_cast<CVersionPackage *>(pkg.get())->m_versionString = m_serverSystem->GetQuestionLibraryVersion();
                pkg->WriteToStream(m_xmlConnection->GetSocketOutputStream());

                m_presentState = stateCheckingDataBase;
            }
            break;

        case stateCheckingDataBase:
            if (result.m_result == CResultPackage::Failed)
            {
                // the question database is out of date, we should update it
                // the (root) server will send new database
                m_presentState = stateReceivingDataBase;
            }
            else
            {
                // the question database is up-to-date, the system's ready
                m_presentState = stateReady;

                // ask the system to start its server part
                //m_serverSystem->StartServer();
                eventReady(this, this);
            }
            break;

        case stateReady:
            if (result.m_result == CResultPackage::Failed)
            {
                if (m_serverSystem->IsExamFinished())
                {
                    m_serverSystem->SendAnswerDatabase();
                }
            }
            break;
        }
    }

    void CServerHandler::DatabasePackageHandler(void)
    {
        CDatabasePackage *pkg = static_cast<CDatabasePackage *>(m_currentPackage.get());
        SharedPtr<CPackage> resultPkg = packageManager.CreatePackage(CResultPackage::packageType);
        if (pkg->IsSucceeded())
        {
            // send success
            static_cast<CResultPackage *>(resultPkg.get())->m_result = CResultPackage::Passed;
            resultPkg->WriteToStream(m_xmlConnection->GetSocketOutputStream());

            m_presentState = stateReady;

            // rename the received file and initialize the server's database
            Poco::File file(pkg->GetFileName());
            m_serverSystem->GetQuestionLibrary().Finalize();
            try {
                file.moveTo(m_serverSystem->GetQuestionLibraryPath());
            } catch (...) {}

            m_serverSystem->InitializeDatabase();

            // ask the system to start its server part
            //m_serverSystem->StartServer();
            eventReady(this, this);
        }
        else
        {
            // failed to receive, ask the (root) server to re-send the database
            static_cast<CResultPackage *>(resultPkg.get())->m_result = CResultPackage::Failed;
            resultPkg->WriteToStream(m_xmlConnection->GetSocketOutputStream());
        }
    }

    void CServerHandler::OrderPackageHandler(void)
    {
        // send the order to client and connected servers
        m_serverSystem->RespondToOrder(*(static_cast<COrderPackage *>(m_currentPackage.get())));
        m_serverSystem->SendExamOrder(*(static_cast<COrderPackage *>(m_currentPackage.get())));
    }

    void CServerHandler::MessagePackageHandler(void)
    {
        // send the message to client and connected servers
        m_serverSystem->SendExamMessage(*(static_cast<CMessagePackage *>(m_currentPackage.get())));
    }

    void CServerHandler::InformationPackageHandler(void)
    {
        // set server system accordingly
        m_serverSystem->SetExamInformation((static_cast<CInformationPackage *>(m_currentPackage.get()))->m_information);

        // send the package to connected server
        m_serverSystem->SendExamInformation(*(static_cast<CInformationPackage *>(m_currentPackage.get())));
    }

    void CServerHandler::RedirectPackageHandler(void)
    {
        if (static_cast<CRedirectPackage *>(m_currentPackage.get())->m_redirected)
        {
            // redirected!
            eventConnectionRedirected(this, static_cast<CRedirectPackage *>(m_currentPackage.get())->m_targetAddress);
            m_presentState = stateRedirecting;
        }
    }

    void CServerHandler::IDAllocPackageHandler(void)
    {
        systemID = static_cast<CIDAllocPackage *>(m_currentPackage.get())->m_ID;
        eventSystemIDUpdated(this, this);
    }

}
}